var ATOM_NS = "http://www.w3.org/2005/Atom";
var ATOM_PUB_INTROSPECT_NS = "http://www.w3.org/2007/app";
var ATOM_PUB_INTROSPECT_MIME_TYPE = "application/atomserv+xml";
var _ioService=Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
var _mimeService=Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);

function _newXMLDocument() {
   return Components.classes["@mozilla.org/xml/xml-document;1"].createInstance().QueryInterface(Components.interfaces.nsIDOMXMLDocument);
}

function _newDOMParser() {
   return Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance().QueryInterface(Components.interfaces.nsIDOMParser);
}

function _newXMLSerializer() {
    return Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance().QueryInterface(Components.interfaces.nsIDOMSerializer);
}


function _Atom_textOf(parent,name,namespace) {
   var text = "";
   var current = parent.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName==name && current.namespaceURI==namespace) {
         text += current.textContent;
      }
      current = current.nextSibling;
   }
   return text;
}

function _Atom_forChild(parent,name,namespace,handler) {
   var current = parent.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName==name && current.namespaceURI==namespace) {
         handler(current);
      }
      current = current.nextSibling;
   }
}

function _Atom_removeChildren(parent) {
   while (parent.childNodes.length>0) {
      parent.removeChild(parent.childNodes.item(0));
   }
}

function AtomService(url,source) {
   this.type = 0;
   this.url = url;
   this.authorization = null;
   this.source = source;
   this.username = null;
   this.author = null;
   this.password = null;
   this.xml = null;
   this.loaded = false;
   this.inprocess = null;
   this.attempted = 0;
   this.workspaces = {};
   this.bufferSize = 8192;
}

AtomService.prototype.introspect = function(options) {
   var currentService = this;
   this.attempted += 1;
   this.inprogress = HTTP("GET",this.url,{
      timeout: options.timeout,
      overrideMimeType: "text/xml",
      username: this.username,
      password: this.password,
      onSuccess: function(status,doc,text) {
         currentService.inprogress = null;
         if (doc) {
            doc.documentElement.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:base",currentService.url);
            currentService.loadIntrospection(doc.documentElement);   
            options.onSuccess(status,text);
         } else {
            options.onFailure(status,doc,text);
         }
      },
      onFailure: function(status,doc,text) {
         currentService.inprogress = null;
         options.onFailure(status,doc,text);
      }
   });
}

AtomService.prototype.loadIntrospection = function(element) {
   this.xml = element;
   this.workspaces = {};
   if (element.namespaceURI!=ATOM_PUB_INTROSPECT_NS) {
      this.loaded = false;
      throw "Introspection document from "+this.url+" is not in the correct namespace: expecting "+ATOM_PUB_INTROSPECT_NS+" and found "+element.namespaceURI;
   }
   if (element.localName!="service") {
      this.loaded = false;
      throw "Introspection document element from "+this.url+" is not {"+ATOM_PUB_INTROSPECT_NS+"}service : {"+element.namespaceURI+"}"+element.localName;
   }
   this.loaded = true;
   var current = element.firstChild;
   var count = 0;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="workspace" && current.namespaceURI==ATOM_PUB_INTROSPECT_NS) {
         count++;
         var id = this.url+"/"+count;
         current.setAttribute("client-id",id);
         this.workspaces[id] = new AtomWorkspace(this,id,current);
      }
      current = current.nextSibling;
   }
}

function AtomWorkspace(service,id,element) {
   this.type = 1;
   this.service = service;
   this.id = id;
   this.xml = element;
   var title = null;
   _Atom_forChild(
      this.xml,
      "title",
      ATOM_NS,
      function(target) { title = target; } 
   );
   if (title) {
      this.title = title.textContent;
   } else {
      this.title = element.getAttribute("title");
   }
   this.collections = {};
   this.children = [];
   this.baseURI = _ioService.newURI(service.url,null,null);
   var current = element.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="collection" && current.namespaceURI==ATOM_PUB_INTROSPECT_NS) {
         var href = current.getAttribute("href");
         if (href) {
            // Make sure the href is absolute
            href = _ioService.newURI(href,null,this.baseURI).spec;
            current.setAttribute("href",href);
            
            var collection = new AtomCollection(this,href,current);
            this.collections[href] = collection;
         }
      }
      current = current.nextSibling;
   }
   this.treeSort();
}

AtomWorkspace.prototype.treeSort = function() {
   var maxLevel = 0;
   for (var href in this.collections) {
      var collection = this.collections[href];
      this._calculateCollectionLevel(collection);
      if (collection.level>maxLevel) {
         maxLevel = collection.level;
      }
   }
   this.parent = null;
   this.children = [];
   for (var href in this.collections) {
      var collection = this.collections[href];
      if (collection.level==0) {
         this.children.push(collection);
         collection.parent = this;
      }
      collection.children = [];
      var next = collection.level + 1;
      for (var otherHref in this.collections) {
         var other = this.collections[otherHref];
         if (other.level==next && otherHref.substring(0,href.length)==href) {
            collection.children.push(other);
            other.parent = collection;
         }
      }
      collection.children.sort(function(x,y) {return x.href < y.href });
   }
   this.children.sort(function(x,y) {return x.href < y.href });
}

AtomWorkspace.prototype._calculateCollectionLevel = function(collection) {
   var count = 0;
   var href = collection.url;
   for (var otherHref in this.collections) {
      if (href!=otherHref && otherHref.length<=href.length && href.substring(0,otherHref.length)==otherHref) {
         count++;
      }
   }
   collection.level = count;
}

AtomWorkspace.prototype.newCollection = function(href,title) {
   var element = this.xml.ownerDocument.createElementNS(ATOM_PUB_INTROSPECT_NS,"collection");
   // Make sure the href is absolute
   href = _ioService.newURI(href,null,this.baseURI).spec;
   element.setAttribute("href",href);
   this.xml.appendChild(element);
   var titleE = this.xml.ownerDocument.createElementNS(ATOM_NS,"title");
   element.appendChild(titleE);
   titleE.appendChild(element.ownerDocument.createTextNode(title));
            
   var collection = new AtomCollection(this,href,element);
   this.collections[href] = collection;
   this.treeSort();
   return collection;
}

AtomWorkspace.prototype.createByPost = function(collection,options) {
   var content = _newXMLDocument();
   var feed = content.createElementNS(ATOM_NS,"feed");
   content.appendChild(feed);
   
   var titleE = content.createElementNS(ATOM_NS,"title");
   titleE.appendChild(content.createTextNode(collection.title));
   content.documentElement.appendChild(titleE);
   var authorE = content.createElementNS(ATOM_NS,"author");
   var authorNameE = content.createElementNS(ATOM_NS,"name");
   authorE.appendChild(authorNameE);
   authorNameE.appendChild(content.createTextNode(this.service.author));
   content.documentElement.appendChild(authorE);
   collection.feed.xml = null;
   collection.feed.entries = {};
   var headers = {};
   if (this.service.authorization) {
      headers["authorization"] = this.service.authorization;
   }

   HTTP("POST",collection.url,{
      timeout: options.timeout,
      username: this.service.username,
      password: this.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      body: content,
      contentType: "application/atom+xml",
      returnHeaders: true,
      onSuccess: function(status,feedDoc,entryText,headers) {
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomWorkspace.prototype.removeByDelete = function(collection,options) {
   var theWorkspace = this;
   var headers = {};
   if (this.service.authorization) {
      headers["authorization"] = this.service.authorization;
   }
   HTTP("DELETE",collection.url,{
      timeout: options.timeout,
      username: this.service.username,
      password: this.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      onSuccess: function(status,feedDoc,entryText,headers) {
         delete theWorkspace.collections[collection.url];
         theWorkspace.xml.removeChild(collection.xml);
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

function AtomCollection(workspace,href,element) {
   this.type = 2;
   this.workspace = workspace;
   this.url = href;
   this.xml = element;
   var title = null;
   _Atom_forChild(
      this.xml,
      "title",
      ATOM_NS,
      function(target) { title = target; } 
   );
   if (title) {
      this.title = title.textContent;
   } else {
      this.title = element.getAttribute("title");
   }
   this.feed = new AtomFeed(this,href);
   this.parent = null;
   this.children = [];
}

function AtomFeed(collection,url) {
   this.type = 3;
   this.collection = collection;
   this.url = url;
   this.loaded = false;
   this.id = null;
   this.xml = null;
   this.entries = {};
}

AtomFeed.prototype.load = function(options) {
   this.xml = null;
   this.loaded = false;
   this.entries = {};
   var current = this;
   var headers = {};
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   HTTP("GET",this.url,{
      timeout: options.timeout,
      username: this.collection.workspace.service.username,
      password: this.collection.workspace.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      onSuccess: function(status,document) {
         current.xml = document.documentElement;
         current.loaded = true;
         current._loadEntries();
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomFeed.prototype._loadEntries = function() {
   this.entries = {};
   if (!this.xml) {
      return;
   }
   var current = this.xml.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="id" && current.namespaceURI==ATOM_NS) {
         this.id = current.textContent;
      } else if (current.localName=="link" && current.namespaceURI==ATOM_NS) {
         var rel = current.getAttribute("rel");
         if (rel=="edit") {
            this.editURL = current.getAttribute("href");
         } else if (rel=="self") {
            this.selfURL = current.getAttribute("href");
         }
      } else if (current.localName=="entry" && current.namespaceURI==ATOM_NS) {
         var entry = new AtomEntry(this,current);
         this.entries[entry.id] = entry;
      }
      current = current.nextSibling;
   }
}

AtomFeed.prototype.updateByPut = function(options) {
   var content = _newXMLDocument();
   var feed = content.createElementNS(ATOM_NS,"feed");
   content.appendChild(feed);
   //var content = this.xml.ownerDocument.implementation.createDocument(ATOM_NS,"feed","");
   //var feed = content.documentElement;
   var current = this.xml.firstChild;
   while (current) {
      if (current.localName=="entry" && current.namespaceURI==ATOM_NS) {
         break;
      }
      feed.appendChild(content.importNode(current,true));
      current = current.nextSibling;
   }
   var headers = {};
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   
   HTTP("PUT",this.url,{
      timeout: options.timeout,
      username: this.collection.workspace.service.username,
      password: this.collection.workspace.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      body: content,
      contentType: "application/atom+xml",
      onSuccess: function(status,feedDoc,entryText,headers) {
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomFeed.prototype.getTitle = function(create) {
   var title = null;
   _Atom_forChild(this.xml,"title",ATOM_NS,function(target) { title = target; } );
   if (create && !title) {
      title = this.xml.ownerDocument.createElementNS(ATOM_NS,"title");
      title.setAttribute("type","text");
      var firstEntry = null;
      _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
      if (firstEntry) {
         this.xml.insertBefore(title,firstEntry);
      } else {
         this.xml.appendChild(title);
      }
   }
   return title ? new AtomText(title) : null;
}

AtomFeed.prototype.getSubtitle = function(create) {
   var subtitle = null;
   _Atom_forChild(this.xml,"subtitle",ATOM_NS,function(target) { subtitle = target; } );
   if (create && !subtitle) {
      subtitle = this.xml.ownerDocument.createElementNS(ATOM_NS,"subtitle");
      subtitle.setAttribute("type","text");
      var firstEntry = null;
      _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
      if (firstEntry) {
         this.xml.insertBefore(subtitle,firstEntry);
      } else {
         this.xml.appendChild(subtitle);
      }
   }
   return subtitle ? new AtomText(subtitle) : null;
}
AtomFeed.prototype.addLink = function(rel,href,type,title) {
   var link = this.xml.ownerDocument.createElementNS(ATOM_NS,"link");
   link.setAttribute("rel",rel);
   link.setAttribute("href",href);
   if (type) {
      link.setAttribute("type",type);
   }
   if (title) {
      link.setAttribute("title",type);
   }
   var firstEntry = null;
   _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
   if (firstEntry) {
      this.xml.insertBefore(link,firstEntry);
   } else {
      this.xml.appendChild(link);
   }
   return link;
}


AtomFeed.prototype.getCategories = function() {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { values[values.length] = new AtomCategory(e); } );
   return values;
}

AtomFeed.prototype.getLinks = function(rel) {
   var values = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,function(e) {
      if (!rel || rel && e.getAttribute("rel")==rel) {
         values[values.length] = new AtomLink(e); 
      }
   });
   return values;
}
AtomFeed.prototype.removeAllLinks = function(matchRel) {
   var toRemove = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,
      function(e) { 
         var rel = e.getAttribute("rel");
         if (matchRel && rel==mathcRel) {
            toRemove[toRemove.length] = e;
         } else if (!matchRel && rel!="edit" && rel!="self") {
            toRemove[toRemove.length] = e;
         }
      }
   );
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}
AtomEntry.prototype.removeLink = function(link) {
   this.xml.removeChild(link.xml);
}


AtomFeed.prototype.addCategory = function(scheme,term,value) {
   var category = this.xml.ownerDocument.createElementNS(ATOM_NS,"category");
   if (scheme && scheme.length>0) {
      category.setAttribute("scheme",scheme);
   }
   category.setAttribute("term",term);
   if (value) {
      category.appendChild(this.xml.ownerDocument.createTextNode(value));
   }     
   var firstEntry = null;
   _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
   if (firstEntry) {
      this.xml.insertBefore(category,firstEntry);
   } else {
      this.xml.appendChild(category);
   }
   return new AtomCategory(category);
}

AtomFeed.prototype.removeCategory = function(category) {
   this.xml.removeChild(category.xml);
}

AtomFeed.prototype.removeAllCategories = function() {
   var toRemove = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { toRemove[toRemove.length] = e; });
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomFeed.prototype.getId = function() {
   var id = null;
   _Atom_forChild(this.xml,"id",ATOM_NS,function(target) { id = target.textContent; } );
   return id;
}

AtomFeed.prototype.addResourceFromFile = function(filePath,options) {
   
   var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
   file.initWithPath(filePath);
   
   var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
   fstream.init(file, 1, 0, 0);
   
   if (!options.contentType) {
      try {
         options.contentType = _mimeService.getTypeFromFile(file);
      } catch (ex) {
         options.contentType = "application/octet-stream";
      }
   }
   
   if (!options.slug) {
      options.slug = file.leafName;
   }
   
   return this.addResourceFromStream(fstream,options);
}

AtomFeed.prototype.addResourceFromStream = function(stream,options) {

   var currentFeed = this;
   var bufferedStream = Components.classes["@mozilla.org/network/buffered-input-stream;1"].createInstance(Components.interfaces.nsIBufferedInputStream);
   bufferedStream.init(stream,this.collection.workspace.service.bufferSize);
   var currentOpenStream = stream;
   var headers = {};
   if (options.slug) {
      headers["slug"] = options.slug;
   }
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   var req = HTTP("POST",this.collection.url,
      {
         username: this.collection.workspace.service.username,
         password: this.collection.workspace.service.password,
         headers: headers,
         contentType: options.contentType,
         body: bufferedStream,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            stream.close();
            if (entryDoc) {
               var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
               currentFeed.xml.appendChild(newXML);
               var newEntry = new AtomEntry(currentFeed,newXML);
               options.onSuccess();
            } else {
               var location = headers["Location"];
               if (!location) {
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
                     currentFeed.xml.appendChild(newXML);
                     var newEntry = new AtomEntry(currentFeed,newXML);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: function(status,xml,text,headers) {
            stream.close();
            options.onFailure(status,xml,text,headers);
         }
      }
   );
      return req;
}

AtomFeed.prototype.addResource = function(content,options) {

   var currentFeed = this;
   var headers = {};
   if (options.slug) {
      headers["slug"] = options.slug;
   }
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   consoleService.logStringMessage("Sending entry to "+this.collection.url+" ...");
   var req = HTTP("POST",this.collection.url,
      {
         username: this.collection.workspace.service.username,
         password: this.collection.workspace.service.password,
         headers: headers,
         contentType: options.contentType,
         body: content,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
               currentFeed.xml.appendChild(newXML);
               var newEntry = new AtomEntry(currentFeed,newXML);
               options.onSuccess();
            } else {
               var location = headers["Location"];
               if (!location) {
                  consoleService.logStringMessage("There was no entry or location header returned.");
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
                     currentFeed.xml.appendChild(newXML);
                     var newEntry = new AtomEntry(currentFeed,newXML);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: function(status,xml,text,headers) {
            consoleService.logStringMessage("Entry failed: "+status+" "+text);
            options.onFailure(status,xml,text,headers);
         }
      }
   );
      return req;
}

AtomFeed.prototype._bootstrap = function() {
   var parser = _newDOMParser();
   var doc = parser.parseFromString("<feed xmlns='http://www.w3.org/2005/Atom'/>","text/xml");
   this.xml = doc.documentElement;
}
AtomFeed.prototype.addEntry = function(title) {
   if (!this.xml) {
      this._bootstrap();
   }
   var doc = this.xml.ownerDocument;
   var entry = doc.createElementNS(ATOM_NS,"entry");
   var idE = entry.ownerDocument.createElementNS(ATOM_NS,"id");
   var id = "temp:"+(new Date()).getTime();
   idE.appendChild(doc.createTextNode(id));
   entry.appendChild(idE);
   var titleE = doc.createElementNS(ATOM_NS,"title");
   titleE.appendChild(doc.createTextNode(title));
   entry.appendChild(titleE);
   var authorE = doc.createElementNS(ATOM_NS,"author");
   var authorNameE = doc.createElementNS(ATOM_NS,"name");
   authorNameE.appendChild(doc.createTextNode(this.collection.workspace.service.author));
   authorE.appendChild(authorNameE);
   entry.appendChild(authorE);
   this.xml.appendChild(entry);
   var aentry = new AtomEntry(this,entry);
   aentry.local = true;
   this.entries[id] = aentry;
   return aentry;
}

AtomFeed.prototype.copyEntry = function(otherEntry) {
   return this.addEntryFromXML(otherEntry.xml);
}

AtomFeed.prototype.addEntryFromXML = function(otherEntry) {
   if (!this.xml) {
      this._bootstrap();
   }
   var entry = this.xml.ownerDocument.importNode(otherEntry,true);
   var id = null;
   _Atom_forChild(entry,"id",ATOM_NS,function(target) { id = target; } );
   if (!id) {
      id = entry.ownerDocument.createElementNS(ATOM_NS,"id");
      entry.appendChild(id);
   }
   var idValue = "temp:"+(new Date()).getTime();
   id.appendChild(this.xml.ownerDocument.createTextNode(idValue));

   var editLink = null;
   _Atom_forChild(entry,"link",ATOM_NS,
       function(target) { 
          if (target.getAttribute("rel")=="edit") { 
             editLink = target; 
          }
       }
   );
   if (editLink) {
      entry.removeChild(editLink);
   }
   this.xml.appendChild(entry);
   var aentry = new AtomEntry(this,entry);
   aentry.local = true;
   this.entries[aentry.id] = aentry;
   return aentry;
}

AtomFeed.prototype.removeEntry = function(entry,options) {
   if (this.entries[entry.id]) {
      if (entry.local) {
         delete this.entries[entry.id];
         this.xml.removeChild(entry.xml);
         if (options) {
            options.onSuccess();
         }
      } else {
         if (!entry.editURL) {
            throw "Entry "+entry.id+" does not have an edit link.";
         }
         var current = this;
         var headers = {};
         if (this.collection.workspace.service.authorization) {
            headers["authorization"] = this.collection.workspace.service.authorization;
         }
         var req = HTTP("DELETE",entry.editURL,{
            username: this.collection.workspace.service.username,
            password: this.collection.workspace.service.password,
            headers: headers,
            timeout: options.timeout,
            overrideMimeType: "text/xml",
            onSuccess: function(status,entryDoc,entryText,headers) {
               delete current.entries[entry.id];
               current.xml.removeChild(entry.xml);
               if (options.onSuccess) {
                  options.onSuccess();
               }
            },
            onFailure: options.onFailure
         });
         return req;
      }
   } else {
      throw "Entry "+entry.id+" does not belong to this feed.";
   }
}

function AtomEntry(feed,entry) {
   this.type = 4;
   this.feed = feed;
   this.xml = entry;
   this.id = null;
   this.local = false;
   this.editURL = null;
   this._idChanged();
   this._editURLChanged();
}

AtomEntry.prototype.addLink = function(rel,href,type,title) {
   var link = this.xml.ownerDocument.createElementNS(ATOM_NS,"link");
   link.setAttribute("rel",rel);
   link.setAttribute("href",href);
   if (type) {
      link.setAttribute("type",type);
   }
   if (title) {
      link.setAttribute("title",type);
   }
   this.xml.appendChild(link);
   return link;
}

AtomEntry.prototype.getAlternateLink = function() {
   var link = null;
   _Atom_forChild(this.xml,"link",ATOM_NS,
   function(target) { 
      if (target.getAttribute("rel")=="alternate") {
         link = target; 
      }
   }
   );
   return link;
}
   

AtomEntry.prototype.getTitle = function() {
   var title = null;
   _Atom_forChild(this.xml,"title",ATOM_NS,function(target) { title = target; } );
   if (!title) {
      throw "Entry "+this.id+" is missing a title.";
   }
   return new AtomText(title);
}
AtomEntry.prototype.getSummary = function(create) {
   var summary = null;
   _Atom_forChild(this.xml,"summary",ATOM_NS,function(target) { summary = target; } );
   if (create && !summary) {
      summary = this.xml.ownerDocument.createElementNS(ATOM_NS,"summary");
      this.xml.appendChild(summary);
      summary.setAttribute("type","text");
   }
   return summary ? new AtomText(summary) : null;
}

AtomEntry.prototype.getContent = function(create) {
   var content = null;
   _Atom_forChild(this.xml,"content",ATOM_NS,function(target) { content = target; } );
   if (create && !content) {
      content = this.xml.ownerDocument.createElementNS(ATOM_NS,"content");
      this.xml.appendChild(content);
      content.setAttribute("type","text");
   }
   return content ? new AtomContent(this,content) : null;
}


AtomEntry.prototype.getLinks = function(rel) {
   var values = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,function(e) {
      if (!rel || rel && e.getAttribute("rel")==rel) {
         values[values.length] = new AtomLink(e); 
      }
   });
   return values;
}

AtomEntry.prototype.removeLink = function(link) {
   this.xml.removeChild(link.xml);
}

AtomEntry.prototype.removeAllLinks = function(matchRel) {
   var toRemove = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,
      function(e) { 
         var rel = e.getAttribute("rel");
         if (matchRel && rel==mathcRel) {
            toRemove[toRemove.length] = e;
         } else if (!matchRel && rel!="edit" && rel!="self") {
            toRemove[toRemove.length] = e;
         }
      }
   );
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomEntry.prototype.getCategories = function() {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { values[values.length] = new AtomCategory(e); } );
   return values;
}

AtomEntry.prototype.addCategory = function(scheme,term,value) {
   var category = this.xml.ownerDocument.createElementNS(ATOM_NS,"category");
   if (scheme && scheme.length>0) {
      category.setAttribute("scheme",scheme);
   }
   category.setAttribute("term",term);
   if (value) {
      category.appendChild(this.xml.ownerDocument.createTextNode(value));
   }     
   this.xml.appendChild(category);
   return new AtomCategory(category);
}

AtomEntry.prototype.removeCategory = function(category) {
   this.xml.removeChild(category.xml);
}

AtomEntry.prototype.removeAllCategories = function() {
   var toRemove = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { toRemove[toRemove.length] = e; });
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomEntry.prototype._xmlChanged = function(xml) {
   if ((xml.localName == "parsererror") &&
       (xml.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml")) {  
      throw "There was a parse error on the returned XML: "+xml.firstChild.textContent;
   }
   if (xml.namespaceURI!=ATOM_NS || xml.localName!="entry") {
      throw "The entry element is {"+xml.namespaceURI+"}"+xml.localName+" which is not {"+ATOM_NS+"}entry as expected.";
   }
   var newXML = this.feed.xml.ownerDocument.importNode(xml,true);
   this.feed.xml.replaceChild(newXML,this.xml);
   this.xml = newXML;
   this._idChanged();
   this._editURLChanged();
}

AtomEntry.prototype._idChanged = function() {
   var id = null;
   _Atom_forChild(this.xml,"id",ATOM_NS,function(target) { id = target.textContent; } );
   if (!id) {
      throw "No id was found in the entry element.";
   }
   if (this.feed.entries[this.id] && id!=this.id) {
      var oldId = this.id;
      delete this.feed.entries[oldId];
   }
   this.id = id;
   this.feed.entries[id] = this;
}

AtomEntry.prototype._editURLChanged = function() {
   this.editURL = null;
   var currentEntry = this;
   _Atom_forChild(this.xml,"link",ATOM_NS,
       function(target) { 
          if (target.getAttribute("rel")=="edit") { 
             currentEntry.editURL = target.getAttribute("href"); 
          }
       }
   );
   if (this.editURL) {
      var baseURI = _ioService.newURI(this.feed.url,null,null);
      this.editURL = _ioService.newURI(this.editURL,null,baseURI).spec;
      if (this.editURL==this.feed.url) {
         this.editURL = null;
      }
   }
}

AtomEntry.prototype.save = function(options) {
   var serializer = _newXMLSerializer();
   var content = serializer.serializeToString(this.xml);
   if (this.local) {
      // new entry
      var currentEntry = this;
      var headers = {};
      if (this.feed.collection.workspace.service.authorization) {
         headers["authorization"] = this.feed.collection.workspace.service.authorization;
      }
      var req = HTTP("POST",this.feed.collection.url,{
         username: this.feed.collection.workspace.service.username,
         password: this.feed.collection.workspace.service.password,
         headers: headers,
         timeout: options.timeout,
         overrideMimeType: "text/xml",
         body: content,
         contentType: "application/atom+xml",
      returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               currentEntry.local = false;
               try {
                  currentEntry._xmlChanged(entryDoc.documentElement);
                  options.onSuccess();
               } catch (ex) {
                  options.onFailure(500,null,ex);
               }
            } else {
               var location = headers["Location"];
               if (!location) {
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  username: this.feed.collection.workspace.service.username,
                  password: this.feed.collection.workspace.service.password,
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     currentEntry._xmlChanged(entryDoc.documentElement);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: options.onFailure
      });
      return req;
   } else {
      // existing entry
      if (!this.editURL) {
         throw new "Cannot save changed to entry "+this.id+" as the entry does not have an edit relation link.";
      }
      var currentEntry = this;
      var headers = {};
      if (this.feed.collection.workspace.service.authorization) {
         headers["authorization"] = this.feed.collection.workspace.service.authorization;
      }
      var req = HTTP("PUT",this.editURL,{
         username: this.feed.collection.workspace.service.username,
         password: this.feed.collection.workspace.service.password,
         headers: headers,
         timeout: options.timeout,
         overrideMimeType: "text/xml",
         body: content,
         contentType: "application/atom+xml",
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               try {
                  currentEntry._xmlChanged(entryDoc.documentElement);
                  options.onSuccess();
               } catch (ex) {
                  options.onFailure(0,null,ex);
               }
            } else {
               var location = headers["Location"];
               if (!location) {
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     currentEntry._xmlChanged(entryDoc.documentElement);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: options.onFailure
      });
      return req;
   }
}

function AtomText(element) {
   this.xml = element;
   this._type = element ? element.getAttribute("type") : null;
}


AtomText.prototype.__defineGetter__("type",function() { return this._type; });
AtomText.prototype.__defineSetter__("type",
   function(v) { 
      this.xml.setAttribute("type",v); 
      this._type = v; 
      this.xml.removeAttribute("src");
      if (this._src) {
         this._src = null;
      }
   }
);

AtomText.prototype.getXHTMLContent = function() {
   var div = null;
   _Atom_forChild(this.xml,"div","http://www.w3.org/1999/xhtml",function(target) { div = target; } );
   return div;
}

AtomText.prototype.getNodeContent = function() {
   var current = this.xml.firstChild;
   while (current) {
      if (current.nodeType==1) {
         return current;
      }
      current = current.nextSibling;
   }
   return null;
}
AtomText.prototype.setTextContent = function(text) {
   _Atom_removeChildren(this.xml);
   this.xml.appendChild(this.xml.ownerDocument.createTextNode(text));
}

AtomText.prototype.setNodeContent = function(node) {
   if (this.xml.ownerDocument!=node.ownerDocument) {
      node = this.xml.ownerDocument.importNode(node,true);
   }
   _Atom_removeChildren(this.xml);
   this.xml.appendChild(node);
}

function AtomContent(entry,element) {
   this.entry = entry;
   this.xml = element;
   this._type = element.getAttribute("type");
   this._src = element.getAttribute("src");
}
AtomContent.prototype = new AtomText();
AtomContent.prototype.__defineGetter__("src",function() { return this._src; });
AtomContent.prototype.__defineSetter__("src",
   function(v) { 
      this.xml.setAttribute("src",v); 
      this._src = src; 
      this._type = null;
      this.xml.removeAttribute("type");
      _Atom_removeChildren(this.xml);
   }
);
AtomContent.prototype.getLocation = function() {
   var baseURI = _ioService.newURI(this.entry.feed.url,null,null);
   return _ioService.newURI(this.xml.getAttribute("src"),null,baseURI).spec;
}

AtomContent.prototype.updateFromFile = function(filePath,options) 
{
   
   var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
   file.initWithPath(filePath);
   
   var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
   fstream.init(file, 1, 0, 0);
   
   if (!options.contentType) {
      try {
         options.contentType = _mimeService.getTypeFromFile(file);
      } catch (ex) {
         options.contentType = "application/binary";
      }
   }
   
   return this.updateFromStream(fstream,options);
}

AtomContent.prototype.updateFromStream = function(stream,options) 
{

   var currentFeed = this;
   var bufferedStream = Components.classes["@mozilla.org/network/buffered-input-stream;1"].createInstance(Components.interfaces.nsIBufferedInputStream);
   bufferedStream.init(stream,this.entry.feed.collection.workspace.service.bufferSize);
   var currentOpenStream = stream;
   var headers = {};
   if (this.entry.feed.collection.workspace.service.authorization) {
      headers["authorization"] = this.entry.feed.collection.workspace.service.authorization;
   }
   var contentType = options.contentType ? options.contentType : this._type;
   var req = HTTP("PUT",this.getLocation(),
      {
         username: this.entry.feed.collection.workspace.service.username,
         password: this.entry.feed.collection.workspace.service.password,
         headers: headers,
         contentType: contentType,
         body: bufferedStream,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            stream.close();
            options.onSuccess();
         },
         onFailure: function(status,xml,text,headers) {
            stream.close();
            options.onFailure(status,xml,text,headers);
         }
      }
   );
   return req;
}

AtomContent.prototype.update = function(content,options) 
{

   var currentFeed = this;
   var headers = {};
   if (this.entry.feed.collection.workspace.service.authorization) {
      headers["authorization"] = this.entry.feed.collection.workspace.service.authorization;
   }
   var contentType = options.contentType ? options.contentType : this._type;
   var req = HTTP("PUT",this.getLocation(),
      {
         username: this.entry.feed.collection.workspace.service.username,
         password: this.entry.feed.collection.workspace.service.password,
         headers: headers,
         contentType: contentType,
         body: content,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            options.onSuccess();
         },
         onFailure: function(status,xml,text,headers) {
            options.onFailure(status,xml,text,headers);
         }
      }
   );
      return req;
}

AtomContent.prototype.getMedia = function(options) 
{
   var req = HTTP("GET",this.getLocation(),
      {
         username: this.entry.feed.collection.workspace.service.username,
         password: this.entry.feed.collection.workspace.service.password,
         onSuccess: options.onSuccess,
         onFailure: options.onFailure
      }
   );
}

function AtomCategory(element) {
   this.xml = element;
   this._scheme = element ? element.getAttribute("scheme") : null;
   this._term = element ? element.getAttribute("term") : null;
}


AtomCategory.prototype.__defineGetter__("scheme",function() { return this._scheme; });
AtomCategory.prototype.__defineSetter__("scheme",
   function(v) { 
      this.xml.setAttribute("scheme",v); 
      this._scheme = v; 
   }
);
AtomCategory.prototype.__defineGetter__("term",function() { return this._term; });
AtomCategory.prototype.__defineSetter__("term",
   function(v) { 
      this.xml.setAttribute("term",v); 
      this._term = v; 
   }
);

AtomCategory.prototype.__defineGetter__("value",function() { return this.xml.textContent; });
AtomCategory.prototype.__defineSetter__("value",
   function(v) { 
      _Atom_removeChildren(this.xml);
      this.xml.appendChild(this.xml.ownerDocument.createTextNode(v)); 
   }
);


function AtomLink(element) {
   this.xml = element;
   this._rel = element ? element.getAttribute("rel") : null;
   this._href = element ? element.getAttribute("href") : null;
   this._type = element ? element.getAttribute("type") : null;
   this._title = element ? element.getAttribute("title") : null;
}


AtomLink.prototype.__defineGetter__("rel",function() { return this._rel; });
AtomLink.prototype.__defineSetter__("rel",
   function(v) { 
      this.xml.setAttribute("rel",v); 
      this._rel = v; 
   }
);

AtomLink.prototype.__defineGetter__("href",function() { return this._href; });
AtomLink.prototype.__defineSetter__("href",
   function(v) { 
      this.xml.setAttribute("href",v); 
      this._href = v; 
   }
);

AtomLink.prototype.__defineGetter__("type",function() { return this._type; });
AtomLink.prototype.__defineSetter__("type",
   function(v) { 
      this.xml.setAttribute("type",v); 
      this._type = v; 
   }
);

AtomLink.prototype.__defineGetter__("title",function() { return this._title; });
AtomLink.prototype.__defineSetter__("title",
   function(v) { 
      this.xml.setAttribute("title",v); 
      this._title = v; 
   }
);
